#pragma once
#include <list>
#include <set>

namespace MkvMux
{

class Ac3Parser
{
    Ac3Parser(const Ac3Parser&);
    Ac3Parser& operator=(const Ac3Parser&);
    
public:

    Ac3Parser();
    ~Ac3Parser();
    
    void Enqueue(const BYTE*, ULONG);
    void EndOfStream();
    
    class Frame
    {
        friend class Ac3Parser;
        
        //Frame();
        Frame(BYTE*, ULONG);        

        ~Frame();                

        Frame(const Frame&);
        Frame& operator=(const Frame&);
        
    public:
    
        static void Destroy(Frame*);
        
        const BYTE* buf() const;  //GetPtr or GetBuf
        ULONG buflen() const;     //GetLength or GetSize or GetBuflen or GetBufLen

        //The Matroska "spec notes" has info about timestamps
        //for audio.  That will influence what we do here.
        ULONG timecode_ms() const;  //GetTimeCodeInMilliseconds
        //TODO: ULONG duration_ms() const;
        //TODO: __int64 samples() const;
        //TODO: ULONG samplerate() const;
        
    private:
    
        BYTE* const m_buf;
        const ULONG m_buflen;        
        ULONG m_timecode_ms;        
        
    };
    
    Frame* Dequeue();
    
private:

    struct Chunk 
    {
        BYTE* const m_buf;
        const ULONG m_buflen;
        ULONG m_off_begin;
        ULONG m_off_end;  //half-open range
        
        Chunk();
        Chunk(const BYTE*, ULONG);
        Chunk(const Chunk&);
        
        bool operator<(const Chunk& rhs) const
        {
            return this->m_buflen < rhs.m_buflen;
        }
        
        ULONG len_front() const;
        ULONG len_back() const;
        
    private:
        static BYTE* Copy(const BYTE*, ULONG);        
        Chunk& operator=(const Chunk&);
    };
    
    typedef std::list<Chunk> chunk_list_t;
    typedef std::set<Chunk> chunk_set_t;
    
    chunk_list_t m_active;    
    chunk_set_t m_free;
    
    ULONG m_len; //total bytes accumulated among all chunks
    
    void Parse();
    
    class ConstIterator  //TODO: really, this is a ChunkIterator
    {
        typedef chunk_list_t::const_iterator iter_t;
        
        const Ac3Parser* const m_parser;
        ULONG m_off;
        iter_t m_chunk_iter;
        ULONG m_chunk_off;
        
        explicit ConstIterator(const Ac3Parser*);
    public:
        static ConstIterator begin(const Ac3Parser*);
        static ConstIterator end(const Ac3Parser*);
        
        ConstIterator(const ConstIterator&);
        ConstIterator& operator=(const ConstIterator&);
        
        BYTE operator*() const;
        ConstIterator& operator++();
        ConstIterator operator++(int);
        bool operator==(const ConstIterator&) const;
        bool operator!=(const ConstIterator&) const;
        ULONG off() const;
        LONG operator-(const ConstIterator&) const;
        void Advance(ULONG);
        ConstIterator operator+(ULONG) const;  //TODO: accept LONG?
    };
    
    ConstIterator begin() const;
    ConstIterator end() const;
    
    class FrameIterator
    {
        FrameIterator(const FrameIterator&);
        FrameIterator& operator=(const FrameIterator&);
    public:
        explicit FrameIterator(Ac3Parser*);
        
        unsigned ReadBits(int nBits);
        unsigned operator()(int nBits);
        
    private:
        ConstIterator m_iter;
        const ConstIterator m_iter_end;
        int m_off;  //bit offset
    };
            
    bool StateFindFrameStart();
    bool StateFindFrameSize();
    bool StateFindFrameStop();

    bool (Ac3Parser::*m_state)();
    
    BYTE Curr() const;
    BYTE Next() const;
    void Consume(ULONG = 1);
    
    typedef std::list<Frame*> frame_list_t;
    frame_list_t m_frames;
    
    static const USHORT s_frmsizcod_table[][38];
    ULONG m_frame_len;  //bytes in current frame
    void ConstructFrame(ULONG);
    
    void SyncFrame();
    void BSI(FrameIterator&);
    
    __int64 m_samples;
    bool m_bEOS;
     
};

}  //end namespace MkvMux
